package ar.fiuba.tp7552.buildadmin.service.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.hibernate.Hibernate;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.springframework.context.support.ApplicationObjectSupport;
import org.springframework.security.acls.domain.GrantedAuthoritySid;
import org.springframework.security.acls.domain.ObjectIdentityImpl;
import org.springframework.security.acls.domain.PrincipalSid;
import org.springframework.security.acls.model.AccessControlEntry;
import org.springframework.security.acls.model.MutableAcl;
import org.springframework.security.acls.model.MutableAclService;
import org.springframework.security.acls.model.NotFoundException;
import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.security.acls.model.Permission;
import org.springframework.security.acls.model.Sid;
import org.springframework.transaction.annotation.Transactional;

import ar.fiuba.tp7552.buildadmin.model.BaseEntity;
import ar.fiuba.tp7552.buildadmin.model.auth.Role;
import ar.fiuba.tp7552.buildadmin.model.auth.User;
import ar.fiuba.tp7552.buildadmin.model.builds.Edificio;
import ar.fiuba.tp7552.buildadmin.model.builds.Propiedad;
import ar.fiuba.tp7552.buildadmin.service.PermissionService;
import ar.fiuba.tp7552.buildadmin.service.PermissionType;
import ar.fiuba.tp7552.buildadmin.util.AliasToPermissionTransformer;
import ar.fiuba.tp7552.buildadmin.util.AliasToResultRowTransformer;
import ar.fiuba.tp7552.buildadmin.util.Cast;
import ar.fiuba.tp7552.buildadmin.util.ResultRow;

@Transactional
public class PermissionServiceImpl extends ApplicationObjectSupport implements PermissionService {

	public static final String PERSISTENCE_UNIT_NAME = "buildadmin";
    @PersistenceContext(unitName=PERSISTENCE_UNIT_NAME)
    private EntityManager entityManager;
	private MutableAclService mutableAclService;
	
	public PermissionServiceImpl(MutableAclService mutableAclService) {
		super();
		this.mutableAclService = mutableAclService;
	}
	
	@Override
	public void addPermission(BaseEntity entity, Role role, Permission permission) {
		addPermission(entity, new GrantedAuthoritySid(role.getAuthority()), permission);
	}
	
	@Override
	public void addPermission(Edificio entity, User user, Permission permission) {
		addPermission(entity, new PrincipalSid(user.getUsername()), permission);
	}
	
    private void addPermission(BaseEntity entity, Sid recipient, Permission permission) {
        MutableAcl acl;
        ObjectIdentity oid = new ObjectIdentityImpl(entity.getClass(), entity.getId());

        try {
            acl = (MutableAcl) mutableAclService.readAclById(oid);
        } catch (NotFoundException nfe) {
            acl = mutableAclService.createAcl(oid);
        }

        acl.insertAce(acl.getEntries().size(), permission, recipient, true);
        mutableAclService.updateAcl(acl);

    }
    
    @Override
	public void deletePermissions(BaseEntity entity) {
    	ObjectIdentity oid = new ObjectIdentityImpl(entity.getClass(), entity.getId());
        mutableAclService.deleteAcl(oid, true);
	}
    
    public List<Long> getObjectIdsByPermissions(User user, PermissionType type) {
    	
    	Session session = (Session) entityManager.getDelegate();
    	
    	String query = 
   			"SELECT DISTINCT acl_object_identity.object_id_identity as id "+
   			"FROM acl_sid "+
   			"INNER JOIN acl_entry ON acl_entry.sid = acl_sid.id "+
   			"INNER JOIN acl_object_identity ON acl_object_identity.id = acl_entry.acl_object_identity "+
   			"INNER JOIN acl_class ON acl_class.id = acl_object_identity.object_id_class "+
   			"WHERE ( acl_sid.sid = :sid AND acl_entry.mask = :mask and acl_sid.principal = 1 ) "
    	;
    	if (user.hasRole(Role.ROLE_ADMIN)) {
    		query+=" OR (  acl_sid.sid = 'ROLE_ADMIN' AND acl_entry.mask = :mask and acl_sid.principal = 0 ) ";
    	}
    	
    	SQLQuery q = session.createSQLQuery(query);
    	q.addScalar("id", Hibernate.LONG);
    	q.setParameter("sid", user.getUsername());	
    	q.setParameter("mask", type.getMask());
    	
    	q.setResultTransformer(new AliasToResultRowTransformer());
    	List<ResultRow> rows = Cast.list(ResultRow.class, q.list());
    	
    	List<Long> ids = new ArrayList<Long>();
    	for (ResultRow row : rows) {
    		ids.add(row.getLong("id"));
    	}
    	return ids;
    }
    
    @Override
	public void deletePermissions(User user) {
        
    	Session session = (Session) entityManager.getDelegate();
    	SQLQuery q = session.createSQLQuery(
    			"SELECT DISTINCT acl_object_identity.object_id_identity AS oid, acl_class.class " +
    			"FROM acl_sid " +
    			"INNER JOIN acl_entry ON acl_entry.sid = acl_sid.id " +
    			"INNER JOIN acl_object_identity ON acl_object_identity.id = acl_entry.acl_object_identity " +
    			"INNER JOIN acl_class ON acl_class.id = acl_object_identity.object_id_class " +
    			"WHERE principal = 1 AND acl_sid.sid = :sid");
    	q.addScalar("oid", Hibernate.LONG);
		q.addScalar("class", Hibernate.STRING);
    	q.setParameter("sid", user.getUsername());	
    	q.setResultTransformer(new AliasToResultRowTransformer());
    	List<ResultRow> rows = Cast.list(ResultRow.class, q.list());
    	for (ResultRow rw : rows) {
    		
    		try {
    			Long id = rw.getLong("oid");
    			Class<? extends BaseEntity> clazzent = Class.forName(rw.getString("class")).asSubclass(BaseEntity.class); 
    			ObjectIdentity oid = new ObjectIdentityImpl(clazzent, id);
    			MutableAcl acl = (MutableAcl) mutableAclService.readAclById(oid);
    			
    			List<AccessControlEntry> entries = acl.getEntries(); 
    			List<AccessControlEntry> todelete = new ArrayList<AccessControlEntry>();
    			
    			for (AccessControlEntry entry : entries) {
    				if (entry.getSid() instanceof PrincipalSid) {
    					PrincipalSid psid = (PrincipalSid)entry.getSid();
    					if ( psid.getPrincipal().equals(user.getUsername())) {
    						todelete.add(entry);
    					}
    				}
    			}
    			
    			for (AccessControlEntry entry : todelete) {
    				int index = entries.indexOf(entry);
    				acl.deleteAce(index);
    			}
    			
    			if (acl.getEntries().size() > 0) {
    				mutableAclService.updateAcl(acl);
    			} else {
    				mutableAclService.deleteAcl(oid, true);
    			}
    			
    		} catch (ClassNotFoundException e) {
    			// no deberia pasar por aca
    			e.printStackTrace();
    		} catch (NotFoundException nfe) {
    			// por aca tampoco.
    			nfe.printStackTrace();
            }
    	}
	}
    
    public Collection<ResultRow> getAll(User user) {
    	Session session = (Session) entityManager.getDelegate();
   	
    	SQLQuery query = session.createSQLQuery(
    		"SELECT DISTINCT sid.sid AS username, edif.nombre as nombre, NOT ISNULL(e1.mask) AS admin, " +
    		"NOT ISNULL(e2.mask) AS gasto, NOT ISNULL(e3.mask) AS cobro, obj.object_id_identity as id " +
    		"FROM acl_object_identity AS obj " +
    		"INNER JOIN acl_entry AS entry ON entry.acl_object_identity = obj.id " +
    		"INNER JOIN acl_sid AS sid ON sid.id = entry.sid " +
    		"LEFT JOIN acl_entry AS e1 ON e1.acl_object_identity = entry.acl_object_identity AND e1.sid = sid.id AND e1.mask = 32 " +
    		"LEFT JOIN acl_entry AS e2 ON e2.acl_object_identity = entry.acl_object_identity AND e2.sid = sid.id AND e2.mask = 64 " +
    		"LEFT JOIN acl_entry AS e3 ON e3.acl_object_identity = entry.acl_object_identity AND e3.sid = sid.id AND e3.mask = 128 " +
    		"LEFT JOIN app_edificio AS edif ON edif.id = obj.object_id_identity " +
    		"WHERE sid.sid = :sid " +
    		"order by id"
    	);
    	query.addScalar("id", Hibernate.LONG);
    	query.addScalar("username", Hibernate.STRING);
    	query.addScalar("nombre", Hibernate.STRING);
    	query.addScalar("admin", Hibernate.BOOLEAN);
    	query.addScalar("gasto", Hibernate.BOOLEAN);
    	query.addScalar("cobro", Hibernate.BOOLEAN);
    	
    	query.setParameter("sid", user.getUsername());	
    	query.setResultTransformer(new AliasToPermissionTransformer());
    	return Cast.list(ResultRow.class, query.list());
    	
    }
    
    public void deletePermission(BaseEntity entity, User user, Permission permission) {
    	try {
		 
			ObjectIdentity oid = new ObjectIdentityImpl(entity.getClass(), entity.getId());
			MutableAcl acl = (MutableAcl) mutableAclService.readAclById(oid);
			
			List<AccessControlEntry> entries = acl.getEntries(); 
			List<AccessControlEntry> todelete = new ArrayList<AccessControlEntry>();
			
			for (AccessControlEntry entry : entries) {
				if (entry.getSid() instanceof PrincipalSid && entry.getPermission().getMask() == permission.getMask() ) {
					PrincipalSid psid = (PrincipalSid)entry.getSid();
					if ( psid.getPrincipal().equals(user.getUsername())) {
						todelete.add(entry);
					}
				}
			}
			
			for (AccessControlEntry entry : todelete) {
				int index = acl.getEntries().indexOf(entry);
				acl.deleteAce(index);
				mutableAclService.updateAcl(acl);
			}
			
			if (acl.getEntries().size() > 0) {
				mutableAclService.updateAcl(acl);
			} else {
				mutableAclService.deleteAcl(oid, true);
			}
    	} catch (NotFoundException nfe) {
			// por aca tampoco.
			nfe.printStackTrace();
        }
    }
    
    public void addPermission(Propiedad entity, User user, Permission permission) {
		addPermission(entity, new PrincipalSid(user.getUsername()), permission);
	}
	
}